iT邦幫忙

2025 iThome 鐵人賽

DAY 22
0
自我挑戰組

數據新手村:統計系畢業生 30 天打怪升級之旅系列 第 22

Day 22 - Pandas 的 Group By 分組聚合

  • 分享至 

  • xImage
  •  

大家好,歡迎來到數據新手村的第二十二天!在過去的幾天裡,我們學會了如何讀取、篩選、清理我們的 Olist 數據集。現在,我們的 DataFrame 已經準備就緒。

但我們很快就會遇到一個瓶頸。我們手上有近 10 萬筆乾淨的訂單資料,但如果老闆走過來問你:

  • 哪個州的平均訂單金額最高?
  • 每個州的訂單總數各是多少?

這些問題,我們用之前學到的篩選技巧,都很難直接回答。因為它們需要的不是「單筆」資料,而是對「分組後」的資料進行彙總計算

今天,我們就要來學習 Pandas 中最強大的分析武器——groupby(),它正是為了解決這類問題而生的。


Group By 的核心思想:拆分-應用-合併 (Split-Apply-Combine)

groupby 的運作原理,可以用一個非常生活化的比喻來理解:洗衣服

想像一下,您有一大籃混雜了各種顏色的髒衣服 (這就是我們原始的 DataFrame)。

  1. Split (拆分):

    • 動作: 您會先把衣服,按照顏色(白色、深色、彩色)分成三堆
    • Pandas 對應: df.groupby('customer_state') 就是這個動作。它會根據 customer_state 這一欄的值,在記憶體中將整個 DataFrame 拆分成不同州份的小群組。
  2. Apply (應用):

    • 動作:每一堆衣服,都執行相同的動作(例如,分別計算每堆衣服的「總重量」)。
    • Pandas 對應: 在分組後的物件上,應用一個「聚合函式」,例如 .sum() (求和), .mean() (求平均), .count() (計數)。
  3. Combine (合併):

    • 動作: 最後,把每一堆的計算結果,整理成一張新的報表(例如:白色衣服 5 公斤、深色衣服 3 公斤...)。
    • Pandas 對應: Pandas 會將每個小群組的計算結果,自動合併成一個新的、以「分組依據」為索引的 SeriesDataFrame

實戰演練:用 Group By 分析 Olist 數據

讓我們用 Olist 數據來實際操作這個流程。首先,我們需要準備一份包含客戶、訂單、支付資訊的合併後 DataFrame。

import pandas as pd

# 讀取需要的資料表
orders_df = pd.read_csv('../../data/olist_datasets/olist_orders_dataset.csv')
customers_df = pd.read_csv('../../data/olist_datasets/olist_customers_dataset.csv')
payments_df = pd.read_csv('../../data/olist_datasets/olist_order_payments_dataset.csv')

# 進行合併 (merge 的細節我們明天會深入探討)
df_merged = pd.merge(orders_df, customers_df, on='customer_id')
df_merged = pd.merge(df_merged, payments_df, on='order_id')

# 只保留我們需要的欄位
df_merged = df_merged[['order_id', 'customer_state', 'customer_city', 'payment_value']]

範例一:單一欄位分組,單一聚合
問題: 計算每個州 (customer_state) 的訂單總數是多少?


# 1. Split: 按 'customer_state' 分組
# 2. Apply: 對 'order_id' 欄位計算唯一值的數量 (nunique)
# 3. Combine: 將結果合併成一個新的 Series
state_order_counts = df_merged.groupby('customer_state')['order_id'].nunique()

# 為了方便觀察,我們對結果進行排序
print(state_order_counts.sort_values(ascending=False).head(10))

輸出結果:
customer_state
SP 41745
RJ 12852
MG 11635
RS 5466
PR 5045
SC 3637
BA 3380
DF 2140
ES 2033
GO 2020
Name: order_id, dtype: int64


範例二:多重聚合 (.agg())
問題: 對於每個州,我們想同時知道訂單總數、總銷售額和平均銷售額。

這時,我們需要使用更靈活的 .agg() 方法。


state_sales_stats = df_merged.groupby('customer_state').agg(
    order_count=('order_id', 'nunique'),     # 對 order_id 計算唯一值數量,並命名為 order_count
    total_sales=('payment_value', 'sum'),      # 對 payment_value 計算總和,並命名為 total_sales
    avg_sales=('payment_value', 'mean')      # 對 payment_value 計算平均值,並命名為 avg_sales
)

# 按總銷售額排序,看看哪個州貢獻最多
print(state_sales_stats.sort_values(by='total_sales', ascending=False).head(10))

輸出結果:
order_count total_sales avg_sales
customer_state
SP 41745 5998226.96 137.504630
RJ 12852 2144379.69 158.525888
MG 11635 1872257.26 154.706434
RS 5466 890898.54 157.180406
PR 5045 811156.38 154.153626
SC 3637 623086.43 165.979337
BA 3380 616645.82 170.816017
DF 2140 355141.08 161.134791
GO 2020 350092.31 165.763404
ES 2033 325967.55 154.706953


範例三:多欄位分組
問題: 我想看得更細!每個州內的每個城市,它們各自的訂單數是多少?

我們只需要在 groupby() 中傳入一個欄位列表即可。


# 1. Split: 同時按 'customer_state' 和 'customer_city' 進行分組
state_city_counts = df_merged.groupby(['customer_state', 'customer_city'])['order_id'].nunique()

print(state_city_counts.head(20))

輸出結果:
customer_state customer_city
AC brasileia 1
cruzeiro do sul 3
epitaciolandia 1
manoel urbano 1
porto acre 1
rio branco 70
senador guiomard 2
xapuri 2
AL agua branca 1
anadia 2
arapiraca 29
atalaia 1
barra de santo antonio 2
barra de sao miguel 2
batalha 3
belem 3
boca da mata 2
cacimbinhas 1
cajueiro 1
campo alegre 2
Name: order_id, dtype: int64

您會發現結果的索引變成了兩層 (MultiIndex),這正是多欄位分組的結果。

Pro Tip: .reset_index()
groupby 的結果索引通常是分組的依據,有時操作起來不太方便。我們可以在最後加上 .reset_index(),將索引變回普通的欄位,得到一個更乾淨的 DataFrame。

# 比較一下加與不加 reset_index() 的差別
print("--- 未使用 reset_index ---")
print(state_sales_stats.sort_values(by='total_sales', ascending=False).head(3))

print("\n--- 使用 reset_index ---")
print(state_sales_stats.sort_values(by='total_sales', ascending=False).reset_index().head(3))

輸出結果:
--- 未使用 reset_index ---
order_count total_sales avg_sales
customer_state
SP 41745 5998226.96 137.504630
RJ 12852 2144379.69 158.525888
MG 11635 1872257.26 154.706434

--- 使用 reset_index ---
customer_state order_count total_sales avg_sales
0 SP 41745 5998226.96 137.504630
1 RJ 12852 2144379.69 158.525888
2 MG 11635 1872257.26 154.706434


結語
今天我們掌握了 Pandas 中最強大的分析武器——groupby。透過「拆分-應用-合併」的思維,我們學會了如何從原始的明細資料中,萃取出具有高度商業價值的洞見。可以說,掌握了 groupby,您就掌握了數據聚合分析的精髓。

學會了縱向的「聚合」,我們還需要橫向的「關聯」。如果數據分散在不同的表格中,我們該如何把它們合併在一起呢?明天,Day 23,我們將學習數據庫的經典操作——Pandas 的 Merge 與 Join。


上一篇
Day 21 - 在 Pandas 中新增與刪除欄位
下一篇
Day 23 - Pandas 的 Merge 與 Join 多表合併
系列文
數據新手村:統計系畢業生 30 天打怪升級之旅24
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言